## (c) 2010-2012 Shadow, Rob Jansen jansen@cs.umn.edu

## ensure cmake version. Setting the upper bound equal to the lower bound
## requests that we don't get new behaviors introduced since that lowest version.
##
## We use the oldest version provided in our supported platforms, which
## is currently 3.18.4 on debian 11.
cmake_minimum_required(VERSION 3.18.4...3.18.4 FATAL_ERROR)

## set build parameters
project(Shadow)
set(SHADOW_VERSION_FULL 3.3.0)

## Most of Shadow's C files are now built via Rust, which means they wouldn't
## be included in the compile_commands.json generated by cmake. Use an external
## tool instead, such as bear.
set(CMAKE_EXPORT_COMPILE_COMMANDS OFF)

set (CMAKE_CXX_STANDARD 11)

message(STATUS "System name: ${CMAKE_SYSTEM_NAME}")
message(STATUS "System version: ${CMAKE_SYSTEM_VERSION}")
message(STATUS "System processor: ${CMAKE_SYSTEM_PROCESSOR}")

## NOTE: Prefer setting C compiler flags in `ShadowBuildCommon::cc_build`, since
## then they will also be used when building code outside of cmake (such as when
## running clippy).

## ensure unix environment (CMAKE_SYSTEM_NAME == "Linux")
if((NOT UNIX) OR (NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux")))
    message(FATAL_ERROR "Shadow requires a Unix/Linux environment.")
endif((NOT UNIX) OR (NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux")))

## ensure out-of-source build
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
    message(FATAL_ERROR "Shadow requires an out-of-source build. Please create a separate build directory and run 'cmake path/to/shadow [options]' there.")
endif(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/" ${CMAKE_MODULE_PATH})
message(STATUS "CMAKE_MODULE_PATH = ${CMAKE_MODULE_PATH}")

## Mismatched C and C++ compilers can result in confusing errors at link time.
## Try to catch it here.
if(((CMAKE_C_COMPILER MATCHES "[/^]gcc") AND (NOT (CMAKE_CXX_COMPILER MATCHES "[/^]g\\+\\+")))
   OR ((CMAKE_C_COMPILER MATCHES "[/^]clang") AND (NOT (CMAKE_CXX_COMPILER MATCHES "[/^]clang\\+\\+"))))
    message(FATAL_ERROR "Mismatched C and C++ compiler ${CMAKE_C_COMPILER} and ${CMAKE_CXX_COMPILER}")
endif()

## https://github.com/shadow/shadow/issues/1741
if((CMAKE_C_COMPILER MATCHES "[/^]clang") AND (CMAKE_C_COMPILER_VERSION VERSION_EQUAL 13.0.0))
	message(FATAL_ERROR "Clang 13.0.0 is not supported (see https://github.com/shadow/shadow/issues/1741)")
endif()

set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

## use the installed shim path only when shadow is installed
##
## We use "$ORIGIN" here to make the shadow installation directory relocatable.
## See also "Rpath token expansion" in ls.do(8).
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")

## get general includes
include(CheckIncludeFile)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(TestBigEndian)
include(ShadowTools)

## general tests and configurations
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON)
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

include(FindPkgConfig)
# Keep consistent with Cargo files that pull in glib: `git grep glib -- '*/Cargo.toml'`
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0>=2.58)

## show a warning if Go is installed and is version 1.21.x
execute_process(COMMAND bash -c "go version | { read _ _ v _; echo \${v#go}; }" OUTPUT_VARIABLE GO_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
if(GO_VERSION MATCHES "^1.21.")
    # https://github.com/shadow/shadow/issues/3267
    message(WARNING "Go version '${GO_VERSION}' is not supported. If running Shadow's extra tests, the Go-based extra tests will fail. See issue #3267.")
endif()

include(CheckSymbolExists)
list(APPEND CMAKE_REQUIRED_INCLUDES ${GLIB_INCLUDE_DIRS})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${GLIB_LIBRARIES})
check_symbol_exists(g_memdup2 "glib.h" HAS_MEMDUP2)

## construct info/version strings
string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+(\\-[a-z]+)?(\\.[0-9]+)?$" "\\1" SHADOW_VERSION_MAJOR ${SHADOW_VERSION_FULL})
string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+(\\-[a-z]+)?(\\.[0-9]+)?$" "\\1" SHADOW_VERSION_MINOR ${SHADOW_VERSION_FULL})
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)(\\-[a-z]+)?(\\.[0-9]+)?$" "\\1" SHADOW_VERSION_PATCH ${SHADOW_VERSION_FULL})

message(STATUS "SHADOW_VERSION_FULL=${SHADOW_VERSION_FULL}")
message(STATUS "SHADOW_VERSION_MAJOR=${SHADOW_VERSION_MAJOR}")
message(STATUS "SHADOW_VERSION_MINOR=${SHADOW_VERSION_MINOR}")
message(STATUS "SHADOW_VERSION_PATCH=${SHADOW_VERSION_PATCH}")

## setup shadow options
option(SHADOW_TEST "build tests (default: OFF)" OFF)
option(SHADOW_WERROR "turn compiler warnings into errors. (default: OFF)" OFF)
option(SHADOW_USE_PERF_TIMERS "compile in timers for tracking the run time of various internal operations. (default: OFF)" OFF)

## display selected user options
MESSAGE(STATUS)
MESSAGE(STATUS "-------------------------------------------------------------------------------")
MESSAGE(STATUS "Current settings: (change with '$ cmake -D<OPTION>=<ON|OFF>')")
MESSAGE(STATUS "SHADOW_TEST=${SHADOW_TEST}")
MESSAGE(STATUS "SHADOW_WERROR=${SHADOW_WERROR}")
MESSAGE(STATUS "SHADOW_EXTRA_TESTS=${SHADOW_EXTRA_TESTS}")
MESSAGE(STATUS "SHADOW_USE_PERF_TIMERS=${SHADOW_USE_PERF_TIMERS}")
MESSAGE(STATUS "-------------------------------------------------------------------------------")
MESSAGE(STATUS)

## now handle the options, set up our own flags
set(CMAKE_C_FLAGS_DEBUG "")
set(CMAKE_C_FLAGS_RELEASE "")

if("${CMAKE_BUILD_TYPE}" STREQUAL "")
    set(CMAKE_BUILD_TYPE "Release")
endif()

message(STATUS "Found CMAKE_BUILD_TYPE='${CMAKE_BUILD_TYPE}'")

# These are redundant for the C code built via Cargo, but still needed for a few
# remaining C modules built by cmake.
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
    message(STATUS "Debug build enabled.")
    add_definitions(-DDEBUG)
    add_compile_options(-O0)
elseif("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
    message(STATUS "Release build enabled.")
    add_definitions(-DNDEBUG)
    add_compile_options(-O3)
else()
    MESSAGE(FATAL_ERROR "Unknown build type '${CMAKE_BUILD_TYPE}'; valid types are 'Release' or 'Debug'")
endif()

# Global compile options for both our Rust and C targets.
# Propagated through to our Rust build in src/CMakeLists.txt.
add_compile_options(
  # Always include debug symbols and frame pointers for ease of debugging
  -ggdb
  -fno-omit-frame-pointer
  # Enable some particularly useful warnings
  -Wreturn-type
  -Wswitch
)

if(SHADOW_WERROR STREQUAL ON)
    add_compile_options(-Werror)
endif(SHADOW_WERROR STREQUAL ON)

if(SHADOW_USE_PERF_TIMERS STREQUAL ON)
    message(STATUS "Perf timers enabled")
    add_definitions(-DUSE_PERF_TIMERS)
endif()

if($ENV{VERBOSE})
    add_definitions(-DVERBOSE)
endif()

#if(POLICY  CMP0026)
#    cmake_policy(SET  CMP0026  OLD)
#endif()

if(SHADOW_TEST STREQUAL ON)
    ## http://www.cmake.org/Wiki/CMake_Testing_With_CTest
    message(STATUS "SHADOW_TEST enabled")
    enable_testing()
endif(SHADOW_TEST STREQUAL ON)

if(CMAKE_BUILD_TYPE STREQUAL Debug)
    message(STATUS "Building Rust library in debug mode.")
    set(RUST_BUILD_TYPE "debug")
    set(RUST_BUILD_FLAG "")
else()
    message(STATUS "Building Rust library in release mode.")
    set(RUST_BUILD_TYPE "release")
    set(RUST_BUILD_FLAG "--release")
endif()

## recurse our project tree
add_subdirectory(${CMAKE_SOURCE_DIR}/src/)
add_subdirectory(${CMAKE_SOURCE_DIR}/examples/)
add_subdirectory(${CMAKE_SOURCE_DIR}/shadowtools/)
